SuiteQL Query Tool
Developer's Guide v2026.1

Welcome to the Developer's Guide for the SuiteQL Query Tool. This guide helps you understand the codebase architecture and learn SuiteScript development through a real-world, production-quality application.

Important: This guide is a companion to the production script file:
suiteql-query-tool.v2026.1.suitelet.js

Line numbers referenced in this guide correspond to that file. Keep both open side-by-side for the best learning experience.

Table of Contents

Quick Start: Your First 5 Minutes

Want to see something working right away? Here's the fastest path to understanding how this tool works.

Step 1: Run Your First Query

Open the SuiteQL Query Tool and paste this simple query into the editor:

SELECT
    ID,
    CompanyName,
    Email
FROM
    Customer
WHERE
    ROWNUM <= 10

Click Run (or press Ctrl+Enter). You should see 10 customer records appear in the results.

Step 2: Understand What Happened

Here's what just occurred behind the scenes:

  1. Your browser sent a POST request to the Suitelet with the query text
  2. The Suitelet's handlePostRequest() function received it Line ~410
  3. It called executeQuery() Line ~660 which used query.runSuiteQL() to execute your SQL
  4. NetSuite returned the data, which was sent back as JSON
  5. The browser JavaScript rendered the results in the table

Step 3: Find This in the Code

Open suiteql-query-tool.v2026.1.suitelet.js and search for:

Step 4: Try Modifying

Now experiment! Try these modifications:

You're Ready! You've just executed a SuiteQL query and traced it through the code. Now you can explore the rest of this guide to understand the deeper concepts, or jump straight to the Key Patterns section.

Line Number Reference

This quick reference table helps you find key sections and functions in the production script. Keep this handy as you explore the code.

Note: Line numbers may shift slightly as the code evolves. Use the section comments (e.g., // SECTION 4:) and function names to locate code if line numbers don't match exactly.

Major Sections

Section Description Lines
Section 1: Configuration CONFIG object, app settings, AI_ENABLED flag, PLUGIN_FOLDER_ID 243-320
Section 2: Module Definition AMD define(), module imports 321-360
Section 3: Request Handlers GET/POST routing, entry point, plugin handler dispatch 361-450
Section 3.5: Plugin System Plugin loading, server hooks, settings persistence 451-650
Section 4: Query Execution executeQuery(), pagination, plugin hooks (onBeforeQuery, onAfterQuery) 651-780
Section 5: File Operations File Cabinet import/export 781-910
Section 6: Workbooks NetSuite Workbooks integration 911-960
Section 6.5: AI Generation All AI provider integrations (7 providers including OpenAI-Compatible) 961-1650
Section 7: Document Generation PDF/HTML template rendering, Generate Suitelet feature 1651-1850
Section 7.5: Cloud Integrations Google Sheets export with RSA-SHA256 JWT signing 1851-2200
Section 8: Tables Reference Database schema browser 2201-2250
Section 9: Main App HTML UI generation, CSS, modals, plugin injection points 2251-5800
Section 10: Client JavaScript Browser-side app logic, SQT namespace, plugin client API, Chart.js integration 5801-12000
Section 11: Tables Reference HTML Schema browser UI with AI integration 12001-13500
Section 12: Schema Explorer Full schema export (12 formats), ERD generation with Mermaid.js 13501-16500

Key Server-Side Functions

Function Purpose Line
handleGetRequest() Renders the HTML UI ~370
handlePostRequest() Routes POST requests to handlers ~410
loadPlugins() Loads plugin files from File Cabinet ~460
executePluginHook() Calls server-side plugin hooks ~520
executeQuery() Runs SuiteQL queries with plugin hooks ~660
importQueryFromFile() Loads SQL from File Cabinet ~790
callAnthropicAPI() Anthropic Claude integration ~1100
callOpenAIAPI() OpenAI GPT integration (also used by OpenAI-Compatible) ~1250
callCohereAPI() Cohere Command integration ~1400
generateDocument() PDF/HTML from templates ~1660
generateSuitelet() Generate standalone Suitelet from template ~1750
getGoogleSheetsToken() Google OAuth JWT token exchange (RSA-SHA256) ~1900
createGoogleSpreadsheet() Google Sheets API - create spreadsheet ~2000
appendToGoogleSheet() Google Sheets API - append data in batches ~2050
generateAppHtml() Main UI HTML generation with plugin injection points ~2260
generateTablesReferenceHtml() Tables browser UI ~12010
generateSchemaExplorerHtml() Schema Explorer UI with 12 export formats ~13510

Key Client-Side Functions (Inside Template Literal)

Function Purpose Approx. Line
runQuery() Executes query via AJAX with plugin hooks ~7500
displayResults() Renders results table with clickable record links ~7800
exportToExcel() XLSX export via SheetJS ~8500
formatSQL() SQL formatter with improved keyword detection ~6800
callAI() Client-side AI call wrapper ~9300
SQT.plugins.register() Register a client-side plugin ~6200
SQT.plugins.executeHook() Execute plugin hooks (onInit, onBeforeQuery, etc.) ~6250
showChart() Display Chart.js visualization modal ~10500
generateRecordLink() Create clickable links to NetSuite records ~7700

Purpose of This Guide

This Developer's Guide is designed to help you learn SuiteScript development by studying a real-world, production application. Rather than maintaining a separate "learning edition" with inline comments, this guide serves as a companion document you can reference alongside the production code.

Who Is This For?

What You'll Learn

Learning Approach: Open the production script file alongside this guide. Use the Line Number Reference to jump to specific code sections as you read each topic.

SuiteScript Primer

Before diving into the code, here's a quick overview of SuiteScript fundamentals.

What is SuiteScript?

SuiteScript is NetSuite's server-side JavaScript API. It allows you to customize and extend NetSuite's functionality through scripts that run on NetSuite's servers. SuiteScript 2.x (the current version) uses the AMD module pattern and supports modern JavaScript features.

JSDoc Decorators

Every SuiteScript file starts with special JSDoc comments that tell NetSuite how to handle the script. Look at the top of the production script Lines 1-5:

/**
 * @NApiVersion 2.1        // Use SuiteScript 2.1 (ES6+ JavaScript)
 * @NScriptType Suitelet   // This is a Suitelet (custom page/endpoint)
 * @NModuleScope Public    // Functions can be called from other scripts
 */

@NApiVersion

@NScriptType

Script Type Purpose Entry Point(s)
Suitelet Custom pages and API endpoints onRequest
User Event Triggers on record events beforeLoad, beforeSubmit, afterSubmit
Client Script Browser-side form interactions pageInit, saveRecord, fieldChanged, etc.
Scheduled Background processing on a schedule execute
Map/Reduce Large-scale data processing getInputData, map, reduce, summarize
RESTlet REST API endpoints get, post, put, delete

The N/ Module System

SuiteScript functionality is organized into modules prefixed with "N/". You import these modules using the AMD define() function. See Lines 323-353 in the production script:

define([
    'N/query',      // SuiteQL queries
    'N/file',       // File Cabinet operations
    'N/runtime'     // Script/user/session info
], function(query, file, runtime) {
    // Your code here
    return {
        onRequest: myFunction
    };
});

Governance

Every SuiteScript operation consumes "governance units." Each script type has a limit:

Script Type Governance Limit
Suitelet 1,000 units
User Event 1,000 units
Client Script 1,000 units
Scheduled Script 10,000 units
Map/Reduce 10,000 units per phase
RESTlet 5,000 units
Common Governance Costs:
query.runSuiteQL() = 10 units
file.load() = 10 units
file.save() = 10 units
https.post() = 10 units
record.load() = 5-10 units
log.debug() = 0 units

Application Architecture

The SuiteQL Query Tool follows a client-server architecture where a single Suitelet serves both the HTML user interface and a JSON API.

Request Flow Diagram

+---------------------------------------------------------------------+
|                         USER'S BROWSER                               |
+---------------------------------------------------------------------+
        |                                          ^
        | 1. GET request                           | 2. HTML page with
        |    (initial page load)                   |    embedded JavaScript
        v                                          |
+---------------------------------------------------------------------+
|                           SUITELET                                   |
|  +-------------------------------------------------------------+    |
|  |  onRequest(context)                                          |    |
|  |    |-- GET  --> handleGetRequest()  --> serverWidget form    |    |
|  |    +-- POST --> handlePostRequest() --> JSON response        |    |
|  +-------------------------------------------------------------+    |
+---------------------------------------------------------------------+
        ^                                          |
        | 3. AJAX POST                             | 4. JSON data
        |    { function: 'queryExecute', ... }     |    { records: [...] }
        |                                          v
+---------------------------------------------------------------------+
|                    BROWSER JAVASCRIPT                                |
|  +-----------------+  +-----------------+  +-----------------+      |
|  | runQuery()      |  | loadSqlFile()   |  | callAI()        |      |
|  | --> fetch() POST|  | --> fetch() POST|  | --> fetch() POST|      |
|  +-----------------+  +-----------------+  +-----------------+      |
+---------------------------------------------------------------------+

Key Architectural Concepts

1. Dual-Purpose Suitelet

The Suitelet handles two types of requests (see Lines 354-430):

2. INLINEHTML for Custom UI

Instead of using NetSuite's built-in form widgets, we inject a complete custom HTML application using the INLINEHTML field type. This gives us full control over the user interface. See ~Line 1650.

3. Client-Side JavaScript

The embedded JavaScript (starting at Line 5461) runs in the browser, not as SuiteScript. It uses standard web APIs like fetch() to communicate with the server. This is regular JavaScript with access to the DOM, localStorage, and third-party libraries.

4. Handler Dispatch Pattern

POST requests include a function property that tells the server which operation to perform. The server uses a dispatch table to route requests (see ~Lines 410-430):

const handlers = {
    'queryExecute': () => executeQuery(context, payload),
    'sqlFileLoad':  () => loadSqlFile(context, payload),
    'sqlFileSave':  () => saveSqlFile(context, payload),
    'aiGenerate':   () => generateAI(context, payload),
    // ... more handlers
};

const handler = handlers[payload.function];
if (handler) handler();

Code Roadmap

The production script is organized into numbered sections. Here's what each section contains:

Section 1: Configuration Lines 243-320
Application settings using Object.freeze() for immutable config. Includes AI_ENABLED flag (Beta 06), PLUGIN_FOLDER_ID (Beta 09), ERD_CONFIG, and feature defaults.
Section 2: NetSuite Module Definition Lines 321-360
The define() call, module imports (N/query, N/file, N/https, etc.), and module reference storage.
Section 3: Request Handlers Lines 361-450
handleGetRequest() and handlePostRequest() - the routing layer that dispatches to specific handlers, including plugin handler support.
Section 3.5: Plugin System Lines 451-650 (Beta 09)
Plugin loading from File Cabinet, server-side hook execution (onBeforeQuery, onAfterQuery, onError), settings persistence, and dependency resolution with topological sorting.
Section 4: Query Execution Lines 651-780
SuiteQL execution with N/query, including pagination logic, virtual views support, and plugin hook integration.
Section 5: File Operations Lines 781-910
File Cabinet operations with N/file - load, save, list files for SQL import/export.
Section 6: Workbooks Lines 911-960
NetSuite Analytics Workbook integration (experimental feature).
Section 6.5: AI Query Generation Lines 961-1650
External API calls with N/https to seven AI services: Anthropic, OpenAI, Cohere, xAI, Google, Mistral, plus OpenAI-Compatible for custom endpoints (Beta 09). API debug modal support (Beta 06).
Section 7: Document Generation Lines 1651-1850
PDF/HTML generation with N/render and session storage with N/runtime. Includes "Generate Suitelet" feature (Beta 07) for creating standalone Suitelets from templates.
Section 7.5: Cloud Integrations Lines 1851-2200
Export to Airtable and Google Sheets. Includes pure JavaScript RSA-SHA256 JWT signing for Google Service Account authentication (Beta 07). Batched exports with progress bar.
Section 8: Tables Reference Lines 2201-2250
Entry point for the Tables Reference secondary page.
Section 9: HTML Generation Lines 2251-5800
Building the custom UI with HTML, CSS, Bootstrap, modals, toolbar, and plugin injection points (17 locations for UI extension).
Section 10: Client-Side JavaScript Lines 5801-12000
The browser-side application logic including:
  • SQT namespace and plugin client API (Beta 09)
  • CodeMirror editor with BUILTIN syntax highlighting (Beta 09)
  • Improved SQL formatter (Beta 09)
  • Chart.js data visualization (Beta 09)
  • Clickable record links (Beta 09)
  • Results rendering with HTML option (Beta 06)
  • Export functions including view-mode-aware copy (Beta 08)
  • AI chat and query assistance
Section 11: Tables Reference HTML Lines 12001-13500
Complete UI for the database schema browser with AI integration, column selection for query generation, and AI Find search mode.
Section 12: Schema Explorer Lines 13501-16500
Full schema export tool with 12 export formats (Beta 08), ERD generation with Mermaid.js, collapsible sections (Beta 09), and label sanitization for malformed API responses (Beta 09).

Key Patterns to Study

These are the most instructive parts of the codebase for learning SuiteScript:

Beginner Basic Query Execution

Location: Section 4, executeQuery() function ~Line 439

Learn how to execute SuiteQL queries and handle the results:

const records = modules.query.runSuiteQL({
    query: 'SELECT ID, CompanyName FROM Customer WHERE IsInactive = ?',
    params: ['F']  // Parameterized query - prevents SQL injection!
}).asMappedResults();  // Returns array of objects

Beginner File Cabinet Operations

Location: Section 5 Lines 564-691

Learn how to read and write files in NetSuite's File Cabinet:

// Loading a file
const fileObj = modules.file.load({ id: fileId });
const contents = fileObj.getContents();

// Creating a file
const newFile = modules.file.create({
    name: 'query.sql',
    contents: sqlText,
    folder: folderId,
    fileType: modules.file.Type.PLAINTEXT
});
const savedId = newFile.save();

Intermediate External API Calls

Location: Section 6.5, AI provider functions Lines 737-1421

Learn how to make HTTPS requests to external services:

const response = modules.https.post({
    url: 'https://api.anthropic.com/v1/messages',
    headers: {
        'Content-Type': 'application/json',
        'x-api-key': apiKey,
        'anthropic-version': '2023-06-01'
    },
    body: JSON.stringify(requestBody)
});

if (response.code === 200) {
    const data = JSON.parse(response.body);
}

Intermediate Session Storage

Location: Section 7, document generation ~Line 1450

Learn how to persist data across requests within a user's session:

// Storing data
const session = modules.runtime.getCurrentSession();
session.set({
    name: 'queryResults',
    value: JSON.stringify(results)
});

// Retrieving data (in a later request)
const stored = session.get({ name: 'queryResults' });
const data = JSON.parse(stored);

Intermediate PDF Generation

Location: Section 7, generateDocument() ~Line 1450

Learn how to generate PDFs from templates with dynamic data:

const renderer = modules.render.create();
renderer.addCustomDataSource({
    alias: 'data',
    format: modules.render.DataSource.OBJECT,
    data: { records: queryResults }
});
renderer.templateContent = xmlTemplate;
const pdfFile = renderer.renderAsPdf();

Advanced Custom UI Architecture

Location: Section 9, generateAppHtml() ~Line 2260

Learn the pattern for building single-page applications inside Suitelets using INLINEHTML, including client-server communication with fetch().

Advanced Nested Template Literals

Location: Section 10 Lines 5801-12000

The client-side JavaScript is embedded inside a template literal. This requires special handling:

Advanced Plugin Hook System (Beta 09)

Location: Section 3.5 Lines 451-650 (server) and Section 10 ~Line 6200 (client)

Learn how to implement a hook-based extensibility system:

Advanced Chart.js Integration (Beta 09)

Location: Section 10 ~Line 10500

Learn how to integrate external visualization libraries:

Intermediate Clickable Record Links (Beta 09)

Location: generateRecordLink() ~Line 7700

Learn how to create intelligent links to NetSuite records based on column names and record types:

// Pattern: Detect ID columns and map to record types
const recordTypeMap = {
    'customer': 'customer',
    'entity': 'customer',
    'vendor': 'vendor',
    'item': 'item',
    'transaction': 'transaction',
    // ... more mappings
};

// Generate URL to record
const url = '/app/common/entity/entity.nl?id=' + recordId;

Intermediate RSA-SHA256 JWT Signing (Beta 07)

Location: Section 7.5 ~Lines 1900-2050

Pure JavaScript implementation of RSA-SHA256 for Google Service Account authentication. This is necessary because NetSuite's N/crypto module lacks RSA private key signing:

Beginner Feature Flags (Beta 06)

Location: CONFIG object ~Line 250

Learn how to implement feature toggles that cleanly enable/disable functionality:

const CONFIG = Object.freeze({
    AI_ENABLED: true,       // Master switch for AI features
    PLUGIN_FOLDER_ID: null, // Enable plugin system
    // ... other settings
});

When AI_ENABLED is false, all AI-related UI elements are hidden and server-side AI requests are rejected.

AI Integration Deep Dive

The SuiteQL Query Tool supports seven AI providers plus a custom "OpenAI-Compatible" option for any OpenAI-compatible API. Understanding this architecture helps you integrate external APIs into your own SuiteScript projects.

Supported Providers

Provider Function Line
Anthropic (Claude) callAnthropicAPI() ~1100
OpenAI (GPT) callOpenAIAPI() ~1250
Cohere (Command) callCohereAPI() ~1400
xAI (Grok) Uses OpenAI-compatible endpoint ~1250
Google (Gemini) Uses OpenAI-compatible endpoint ~1250
Mistral AI Uses OpenAI-compatible endpoint ~1250
OpenAI-Compatible (Custom) Uses callOpenAIAPI() with custom base URL ~1250

OpenAI-Compatible Provider (Beta 09)

The "OpenAI-Compatible" option allows you to use any API that follows the OpenAI chat completions format. This includes:

Configuration requires a custom base URL and model name, making it flexible for enterprise deployments or self-hosted solutions.

Integration Pattern

All AI integrations follow a similar pattern:

  1. Receive API key and prompt from client
  2. Build request payload according to provider's API spec
  3. Make HTTPS POST request using N/https
  4. Parse response and extract generated text
  5. Return JSON response to client
Study Tip: Compare callAnthropicAPI() and callOpenAIAPI() to see how different APIs require different request formats but follow the same overall pattern.

Schema Explorer Architecture

The Schema Explorer (Lines 13501-16500) is a sophisticated tool that builds a complete picture of your NetSuite database schema.

Key Features

Export Formats (Beta 08)

The Schema Explorer now supports 12 export formats organized into "Common" and "Advanced" groups:

Format Use Case
JSON Universal format for programmatic access
MySQL / PostgreSQL DDL Replicate schema in relational databases
BigQuery / Snowflake DDL Cloud data warehouse integration
Amazon Redshift DDL AWS data warehouse
dbt YAML dbt Core/Cloud source configuration
Apache Avro Kafka/streaming data pipelines
Markdown Documentation for GitHub, Confluence, Notion
DBML dbdiagram.io ERD generation
Graphviz DOT OmniGraffle, Graphviz tools

ERD Generation

The ERD viewer uses Mermaid.js to render entity-relationship diagrams. Key concepts:

Plugin Architecture

Beta 09 introduced a comprehensive plugin system that allows developers to extend the SuiteQL Query Tool without modifying the core codebase. This is one of the most instructive parts of the application for learning extensibility patterns.

Plugin System Overview

Plugins are self-contained JavaScript or JSON files stored in the NetSuite File Cabinet. The system supports:

Enabling Plugins

Set CONFIG.PLUGIN_FOLDER_ID to the internal ID of a File Cabinet folder containing plugin files. Plugin files must be named with *.sqt-plugin.js or *.sqt-plugin.json extension.

Server-Side Hooks

Hook Purpose Parameters
onBeforeQuery Modify or cancel queries before execution { query, params }
onAfterQuery Process results after execution { query, results, elapsed }
onError Handle query errors { query, error }

Client-Side Hooks

Hook Purpose
onInit Called when app initialization completes
onBeforeQuery Modify or cancel queries from client
onAfterQuery Process successful query results
onResultsDisplay Customize result rendering
onBeforeExport / onAfterExport Export lifecycle hooks
onEditorChange Respond to editor changes

UI Injection Points (17 total)

Toolbar
toolbar-start, toolbar-end, more-dropdown, ai-dropdown
Header
header-right
Editor
before-editor, editor-toolbar, nl-bar
Results
results-header, results-footer (dynamic via hooks)
Sidebar
sidebar-section
Modals
export-menu, local-library-actions, modals
Other
options-panel, status-bar

Public API for Plugins (SQT Namespace)

// Plugin management
SQT.plugins.register(pluginConfig);
SQT.plugins.get('plugin-id');
SQT.plugins.executeHook('onAfterQuery', data);

// Data access
SQT.getResults();       // Get current query results
SQT.getQuery();         // Read editor content
SQT.setQuery(sql);      // Write to editor
SQT.getEditor();        // Access CodeMirror instance

// UI control
SQT.showModal(id);      // Show a Bootstrap modal
SQT.hideModal(id);      // Hide a modal

Sample Plugin

A sample plugin (query-logger.sqt-plugin.js) is included that demonstrates:

Learning Resource: See PLUGIN-GUIDE.md for a complete plugin development guide with examples and best practices.

Data Visualization with Chart.js

Beta 09 added data visualization capabilities powered by Chart.js, allowing users to create charts from query results.

Supported Chart Types

Chart Configuration

Users can configure charts in two ways:

  1. Manual configuration - Select label column (X-axis) and value column(s) (Y-axis)
  2. AI-assisted - Describe the desired chart in natural language and let AI configure it

Key Features

Integration Pattern

The Chart.js integration demonstrates how to:

// Example: Creating a chart from results
const ctx = document.getElementById('chartCanvas').getContext('2d');
new Chart(ctx, {
    type: 'bar',
    data: {
        labels: results.map(r => r[labelColumn]),
        datasets: [{
            label: valueColumn,
            data: results.map(r => r[valueColumn]),
            backgroundColor: getThemeColors()
        }]
    }
});

Suggested Learning Path

Here's a recommended order for exploring the codebase:

Phase 1: Understand the Structure

  1. Read the JSDoc decorators at the top of the file Lines 1-5
  2. Study the define() call and module imports Lines 321-360
  3. Understand the onRequest entry point and how it routes GET vs POST Lines 361-450
  4. Review the CONFIG object and feature flags Lines 243-320

Phase 2: Learn Core Operations

  1. Study executeQuery() - this is the heart of the application ~Line 660
  2. Explore file operations - loading and saving SQL files Lines 781-910
  3. Understand handlePostRequest() and the dispatch pattern ~Line 410

Phase 3: Advanced Patterns

  1. Study external API calls in the AI generation section Lines 961-1650
  2. Explore PDF generation with the render module ~Line 1660
  3. Understand session storage for multi-step processes
  4. Study RSA-SHA256 JWT signing for Google Sheets export ~Lines 1900-2050

Phase 4: UI Architecture

  1. See how INLINEHTML is used to inject custom HTML ~Line 2260
  2. Study the client-side JavaScript and its communication with the server Lines 5801-12000
  3. Understand the fetch() pattern for AJAX calls
  4. Explore Chart.js integration for data visualization ~Line 10500

Phase 5: Extensibility (Beta 09)

  1. Study the plugin system server-side hooks Lines 451-650
  2. Explore client-side plugin API (SQT namespace) ~Line 6200
  3. Understand UI injection points for extending the interface
  4. Review the sample plugin (query-logger.sqt-plugin.js) for practical examples
Study Tip: As you read the code, try modifying small things and deploying to see the effects. Experimentation is the best teacher!
New in 2026.1: For plugin development, see the PLUGIN-GUIDE.md file for comprehensive documentation on creating your own plugins, including hook signatures, settings persistence, and UI injection patterns.

Glossary of NetSuite Terms

NetSuite has its own vocabulary. Here are key terms you'll encounter:

Term Definition
Internal ID A unique numeric identifier assigned to every record in NetSuite. Used in scripts to reference specific records.
Script ID A human-readable identifier you assign when creating scripts, custom fields, or custom records. Always prefixed (e.g., customscript_my_suitelet).
Deployment An instance of a script configured to run. One script can have multiple deployments with different settings.
File Cabinet NetSuite's file storage system. Organized into folders, stores scripts, images, documents.
Governance NetSuite's resource management system. Each script operation costs "units" and scripts have limits.
Record Type A category of data in NetSuite (e.g., Customer, Sales Order, Invoice).
Role A set of permissions assigned to users. Determines what records and scripts a user can access.
Sandbox A copy of your production NetSuite account for testing.
BUILTIN.DF() "Display Field" - A SuiteQL function that returns the display name instead of the internal ID.
Hook A function callback that plugins can register to intercept or modify application behavior at specific points (e.g., before query execution).
Injection Point A predefined location in the UI where plugins can insert custom HTML elements.
IndexedDB Browser-based database API used by Schema Explorer to persist schema data locally for fast access.
JWT JSON Web Token - A compact, URL-safe token format used for authentication. The Google Sheets integration uses JWT with RSA-SHA256 signing.

Deployment Walkthrough

Here's how to deploy the SuiteQL Query Tool (or any Suitelet) to NetSuite:

Step 1: Upload the Script File

  1. Go to Documents > Files > File Cabinet
  2. Navigate to SuiteScripts folder (create if needed)
  3. Click Add File and upload the script

Step 2: Create the Script Record

  1. Go to Customization > Scripting > Scripts > New
  2. Select the uploaded script file
  3. Fill in: Name, ID, Description
  4. Click Save

Step 3: Create a Deployment

  1. On the Script record, go to Deployments subtab
  2. Click New Deployment
  3. Set: Title, ID, Status (Released), Log Level
  4. On Audience subtab, select allowed roles
  5. Click Save

Step 4: Access the Suitelet

Click the URL link on the deployment record to open the tool.

Common Errors & Troubleshooting

SSS_USAGE_LIMIT_EXCEEDED

Error: "You have exceeded the governance limit for this script."

Solutions: Reduce operations, use getRemainingUsage() to monitor, or use Map/Reduce for large operations.

Invalid Column / Invalid Table

Error: "Invalid search column" or "Invalid table"

Solutions: Check column/table names in Analytics Browser, verify role permissions, column names are case-sensitive.

Search Timed Out

Error: "Search timed out."

Solutions: Add restrictive WHERE clauses, remove unnecessary JOINs, avoid SELECT *, add date range filters.

SuiteQL Quick Reference

Common Tables

Table Description
TransactionAll transactions (orders, invoices)
TransactionLineLine items on transactions
CustomerCustomer records
VendorVendor records
EmployeeEmployee records
ItemAll item types
AccountChart of accounts

Useful Functions

-- Get display value instead of internal ID
BUILTIN.DF( Transaction.status ) AS StatusName

-- Handle NULL values
NVL( Customer.phone, 'No Phone' ) AS Phone

-- Date formatting
TO_CHAR( Transaction.trandate, 'YYYY-MM-DD' ) AS FormattedDate

-- Current date
SYSDATE

-- Limit rows (no LIMIT keyword in SuiteQL!)
WHERE ROWNUM <= 100

Security Best Practices

1. Always Use Parameterized Queries

// GOOD - Parameterized (safe)
query.runSuiteQL({
    query: 'SELECT * FROM Customer WHERE id = ?',
    params: [customerId]
});

// BAD - String concatenation (SQL injection risk!)
query.runSuiteQL({
    query: 'SELECT * FROM Customer WHERE id = ' + customerId
});

2. Never Expose API Keys in Client-Side Code

Store keys in Script Parameters or have users enter them (stored in session).

3. Validate and Sanitize Input

Always validate data from external sources before using it.

Debugging Tips

1. Use the Execution Log

log.debug({ title: 'Debug', details: myVariable });
log.error({ title: 'Error', details: e.message });

View logs: Script Deployment record → View Execution Log

2. Browser Developer Tools

For client-side JavaScript: Console, Network tab, Sources tab for breakpoints.

3. Check Governance

const remaining = runtime.getCurrentScript().getRemainingUsage();
log.debug('Governance remaining', remaining);

Frequently Asked Questions

Q: Why can't I use LIMIT like in MySQL?

A: SuiteQL is based on Oracle SQL. Use ROWNUM instead: WHERE ROWNUM <= 10

Q: How do I find table and column names?

A: Use Analytics Browser in NetSuite, the Tables Reference in this tool, or query OA_TABLES.

Q: Can I INSERT, UPDATE, or DELETE with SuiteQL?

A: No. SuiteQL is read-only. Use the N/record module to modify data.

Q: Why do I get numbers instead of names for some fields?

A: Use BUILTIN.DF(field) to get the display value instead of internal ID.

Q: How do I enable the plugin system?

A: Set CONFIG.PLUGIN_FOLDER_ID to the internal ID of a File Cabinet folder. Place plugin files (*.sqt-plugin.js or *.sqt-plugin.json) in that folder.

Q: How do I disable AI features?

A: Set CONFIG.AI_ENABLED to false. This hides all AI-related UI elements and rejects AI requests on the server.

Q: Can I use my own AI model or API?

A: Yes! Use the "OpenAI-Compatible" provider option to connect to any API that follows the OpenAI chat completions format (OpenRouter, Azure OpenAI, Ollama, etc.).

Q: How do I create charts from query results?

A: Click the "Chart" button in the results toolbar after running a query. Select chart type and configure the label/value columns, or describe what you want and let AI configure it.

Q: Why don't ID columns link to records?

A: The "Link IDs to records" option may be disabled in the Options panel. Enable it to automatically create clickable links for customer, vendor, employee, item, and transaction IDs.

Printable Cheat Sheet

SuiteQL & SuiteScript Cheat Sheet

Common Tables

Transaction, TransactionLine, Customer, Vendor, Employee, Item, Account

N/ Modules

N/query, N/record, N/file, N/https, N/runtime, N/log, N/render

Plugin Hooks (Server)

onBeforeQuery, onAfterQuery, onError

Plugin Hooks (Client)

onInit, onBeforeQuery, onAfterQuery,
onResultsDisplay, onEditorChange

Key Functions

BUILTIN.DF(field)
NVL(field, default)
TO_DATE('2024-01-01', 'YYYY-MM-DD')
SYSDATE
ROWNUM

Governance

query.runSuiteQL() = 10 units
record.load() = 5-10 units
Suitelet limit = 1,000 units

SQT Plugin API

SQT.getResults()
SQT.getQuery() / setQuery()
SQT.plugins.register(config)
SQT.showModal(id)

Additional Resources

Official Documentation

Community Resources